﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Globalization;

namespace SQLite2CELib
{
    public class LinqToSqlClassGenerator
    {
        private const string SqlMetalCmdFormat = "\"{0}\" /code:\"{1}\"";
        private StringBuilder _sbReport;
        private bool _wantVerboseReport;

        /// <summary>
        /// Method to generate the cs file that contains LINQ To SQL classes for the SqlCedb.
        /// </summary>
        /// <param name="ceDBPath">Entire location of sqlCe db file</param>
        /// <returns>Name of file containing code</returns>
        public String Generate(String ceDBPath, StringBuilder sbReport, bool wantVerboseReport)
        {
            String className, fileNameWithExtn;
            String sqlMetalCommandArgs;

            _sbReport = sbReport;
            _wantVerboseReport = wantVerboseReport;

            //Generate file cs file name that will contain auto generated code.
            className = ExtractCeDbFileName(ceDBPath);
            fileNameWithExtn = className + ".cs";

            string dirName = ExtractCeDbDirName(ceDBPath);

            //Prepare command.
            sqlMetalCommandArgs = String.Format(CultureInfo.InvariantCulture, SqlMetalCmdFormat, ceDBPath, dirName + "\\" + fileNameWithExtn);

            //Execute the command.
            ExecCommand(sqlMetalCommandArgs);
            //Comment out incompatible constructor signatures.
            CommentOutIncompatibleCtor(className, dirName + "\\" + fileNameWithExtn);

            return fileNameWithExtn;
        }

        private void ExecCommand(string sqlMetalCommandArgs)
        {
            ProcessStartInfo cmdInfo = new ProcessStartInfo();
            cmdInfo.FileName = ".\\SqlMetal.exe";
            cmdInfo.Arguments = sqlMetalCommandArgs;
            cmdInfo.RedirectStandardOutput = true;
            cmdInfo.RedirectStandardError = true;
            cmdInfo.RedirectStandardInput = true;
            cmdInfo.UseShellExecute = false;

            using (Process cmdProcess = new Process())
            {
                cmdProcess.StartInfo = cmdInfo;
                cmdProcess.ErrorDataReceived += cmd_Error;
                cmdProcess.OutputDataReceived += cmd_DataReceived;
                cmdProcess.EnableRaisingEvents = true;
                cmdProcess.Start();
                cmdProcess.BeginOutputReadLine();
                cmdProcess.BeginErrorReadLine();
                cmdProcess.WaitForExit();
            }
        }

        private void cmd_DataReceived(object sender, DataReceivedEventArgs e)
        {
            if (_wantVerboseReport && !string.IsNullOrEmpty(e.Data))
            {
                _sbReport.AppendLine("SqlMetal output: " + e.Data);
            }
        }

        private void cmd_Error(object sender, DataReceivedEventArgs e) 
        {
            if (!string.IsNullOrEmpty(e.Data))
            {
                _sbReport.AppendLine("SqlMetal error: " + e.Data);
            }
        }

        private string ExtractCeDbFileName(string ceDbPath)
        {
            FileInfo ceDbFileInfo;

            ceDbFileInfo = new FileInfo(ceDbPath);
            return ceDbFileInfo.Name.Substring(0, ceDbFileInfo.Name.LastIndexOf(".", StringComparison.Ordinal));
        }

        private string ExtractCeDbDirName(string ceDbPath)
        {
            FileInfo ceDbFileInfo;

            ceDbFileInfo = new FileInfo(ceDbPath);
            return ceDbFileInfo.DirectoryName;
        }

        private void CommentOutIncompatibleCtor(string className, string fileNameWithExtn)
        {
            String fileWithCommentedCtor;

            List<String> ctorSignatures = new List<String> 
            {
                "public {0}(System.Data.IDbConnection connection)",
                "public {0}(string connection, System.Data.Linq.Mapping.MappingSource mappingSource)",
                "public {0}(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource)"
            };

            //Format constructor signatures by adding class names.
            for (int i = 0; i < ctorSignatures.Count; i++)
            {
                ctorSignatures[i] = String.Format(CultureInfo.InvariantCulture, ctorSignatures[i], className);
            }

            fileWithCommentedCtor = CreateFileWithCommentedCtor(fileNameWithExtn, ctorSignatures);

            //Replace actual file with file containing commented Ctor.
            File.Delete(fileNameWithExtn);
            File.Move(fileWithCommentedCtor, fileNameWithExtn);
        }

        private String CreateFileWithCommentedCtor(String actualCodeFilePath, List<String> ctorSignatures)
        {
            String currentLOC;
            bool doesCtorEnd;
            String fileNameWithCommentedCtor;

            doesCtorEnd = true;
            fileNameWithCommentedCtor = Guid.NewGuid() + ".cs";
            string fullFileNameWithCommentedCtor = Environment.GetEnvironmentVariable("TEMP") + "\\" + fileNameWithCommentedCtor;

            using (FileStream linqClassStream = new FileStream(actualCodeFilePath, FileMode.Open))
            using (FileStream linqClassStreamWithCommentedCtor = new FileStream(fullFileNameWithCommentedCtor, FileMode.Create))
            {
                using (StreamReader reader = new StreamReader(linqClassStream))
                using (StreamWriter writer = new StreamWriter(linqClassStreamWithCommentedCtor))
                {
                    while (reader.EndOfStream == false)
                    {
                        currentLOC = reader.ReadLine();
                        if (IsMatch(currentLOC, ctorSignatures) == true)
                        {
                            doesCtorEnd = false;
                        }

                        if (doesCtorEnd == false)
                        {
                            writer.WriteLine("//" + currentLOC);
                        }
                        else
                        {
                            writer.WriteLine(currentLOC);
                        }

                        if (currentLOC.Contains("}") == true)
                        {
                            doesCtorEnd = true;
                        }
                    }
                }
                return fullFileNameWithCommentedCtor;
            }
        }

        private bool IsMatch(String signatureToMatch, List<String> ctorSignatures)
        {
            int matchCount = ctorSignatures.Where(eachSignature => signatureToMatch.ToLowerInvariant().IndexOf(eachSignature.ToLowerInvariant(), StringComparison.OrdinalIgnoreCase) != -1).Count();
            return matchCount > 0;                
        }
    }
}
